package lee.vioson.ebook.viewmodels

import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
import lee.vioson.composecommon.base.mvi.Reducer
import lee.vioson.composecommon.base.mvi.UiEvent
import lee.vioson.composecommon.base.mvi.UiState
import lee.vioson.ebook.data.AppContainer
import lee.vioson.ebook.utils.createChapterFile
import lee.vioson.ebook.utils.zipDefaultExtractDir
import lee.vioson.remotedata.bean.CachedChapter
import lee.vioson.remotedata.bean.ChapterInfo
import lee.vioson.remotedata.bean.DownloadState
import lee.vioson.remotedata.manager.BookCacheManager
import net.lingala.zip4j.ZipFile
import java.io.File

/**
 *Author:viosonlee
 *Date:2023/6/20
 *DESCRIPTION:
 */
class ReadPageViewModel(
    private val appContainer: AppContainer,
    private val bookId: String,
    private val chapterId: String
) : AppViewModel<ReadPageViewModel.State, ReadPageViewModel.Event>(appContainer.appEvent) {

    private val reducer = SubReducer(State())

    override val state: StateFlow<State>
        get() = reducer.state

    override fun sendEvent(event: Event) {
        reducer.sendEvent(event)
    }

    fun checkCachedAndDownload(chapterInfo: ChapterInfo) = BookCacheManager.getCachedChapterInfo(
        appContainer.context,
        bookId, chapterId
    )
        .onEach { downloadZip(chapterInfo, it).launchIn(viewModelScope) }
        .catch { handleException(it) }
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), DownloadState.InProgress(0))

    private fun downloadZip(
        chapterInfo: ChapterInfo,
        cachedChapter: CachedChapter?
    ): StateFlow<DownloadState> {
        val zipFile = appContainer.context.createChapterFile(
            bookId = bookId,
            chapterId = chapterId,
            fileName = chapterInfo.content.substringAfterLast("/")
        )
        val hasCached =
            cachedChapter != null && (cachedChapter.contentLength == File(cachedChapter.filePath).length())
        return if (hasCached) {
            flow { emit(DownloadState.Cached(File(cachedChapter!!.filePath))) }
        } else {
            appContainer.cacheFileRepository.downloadFile(
                url = chapterInfo.content,
                file = zipFile
            )
        }
            .catch { handleException(it) }
            .onEach {
                if (it is DownloadState.Success) {
                    val now = System.currentTimeMillis()
                    BookCacheManager.cachedChapterInfo(
                        appContainer.context,
                        cachedChapter = CachedChapter(
                            bookId,
                            chapterId,
                            it.contentLength,
                            now,
                            now,
                            it.file.path
                        )
                    )
                    extractZip(it.file, chapterInfo.publicKey, zipFile.zipDefaultExtractDir.path)
                }
                if (it is DownloadState.Cached) {
                    extractZip(it.file, chapterInfo.publicKey, zipFile.zipDefaultExtractDir.path)
                }
                sendEvent(Event.OnDownloadStateChange(it))
            }
            .stateIn(
                viewModelScope,
                SharingStarted.WhileSubscribed(5000),
                DownloadState.InProgress(0)
            )
    }

    private fun extractZip(
        zip: File,
        password: String,
        outputPath: String = File(zip.parentFile, "/extract").path
    ) = with(File(outputPath)) {
        flow {
            val listFiles = listFiles()
            if (!listFiles.isNullOrEmpty()) {
                emit(listFiles.toMutableList())
            }
            val list = withContext(Dispatchers.IO) {
                try {
                    ZipFile(zip, password.toCharArray()).extractAll(outputPath)
                    listFiles()?.toMutableList()
                } catch (e: Exception) {
                    error.postValue(e)
                    emptyList<File>()
                }
            }
            emit(list)
        }
            .flowOn(Dispatchers.IO)
            .catch { handleException(it) }
            .onStart { sendEvent(Event.OnExtractingChange(true)) }
            .onCompletion { sendEvent(Event.OnExtractingChange(false)) }
            .onEach { sendEvent(Event.OnPicListChange(it ?: emptyList())) }
            .stateIn(
                viewModelScope,
                SharingStarted.WhileSubscribed(5000),
                emptyList()
            ).launchIn(viewModelScope)
    }

    private class SubReducer(initialState: State) :
        Reducer<State, Event>(initialState) {
        override fun reduce(oldState: State, event: Event) {
            when (event) {
                is Event.OnDownloadStateChange -> setState(oldState.copy(downloadState = event.downloadState))
                is Event.OnPicListChange -> setState(oldState.copy(picList = event.list))
                is Event.OnExtractingChange -> setState(oldState.copy(extracting = event.extracting))
            }
        }
    }


    sealed class Event : UiEvent {
        data class OnDownloadStateChange(val downloadState: DownloadState) : Event()
        data class OnPicListChange(val list: List<File>) : Event()

        data class OnExtractingChange(val extracting: Boolean) : Event()
    }

    data class State(
        val downloadState: DownloadState = DownloadState.InProgress(0),
        val picList: List<File> = emptyList(),
        val extracting: Boolean = false,
    ) : UiState
}

